/*
* @Author: liqs
* @Date:   2021/10/21 14:30
 */
package fabric

import (
	"encoding/json"
	"fmt"
	"github.com/gogo/protobuf/proto"
	"github.com/hyperledger/fabric-protos-go/common"
	"github.com/hyperledger/fabric-protos-go/ledger/rwset"
	"github.com/hyperledger/fabric-protos-go/ledger/rwset/kvrwset"
	"github.com/hyperledger/fabric-protos-go/msp"
	pb "github.com/hyperledger/fabric-protos-go/peer"
	"github.com/hyperledger/fabric-sdk-go/pkg/client/channel"
	"github.com/hyperledger/fabric-sdk-go/pkg/client/ledger"
	"github.com/hyperledger/fabric-sdk-go/pkg/common/errors/retry"
	"github.com/hyperledger/fabric-sdk-go/pkg/core/config"
	"github.com/hyperledger/fabric-sdk-go/pkg/fabsdk"
	"github.com/pkg/errors"
	"log"
	"time"
)

func initFuctionName() (f FuctionName) {
	f.GetRecordByKey = "GetRecordByKey"
	f.PutStringRecordByKey = "PutStringRecordByKey"
	f.GetRecordBySelectorWithPagination = "GetRecordBySelectorWithPagination"
	f.GetRecordCountBySelector = "GetRecordCountBySelector"
	f.GetHistoryByKey = "GetHistoryByKey"
	return f
}

type BlockchainForFabric struct {
	BlockchainForFabricInfo BlockchainForFabricInfo
	fuctionName             FuctionName
	Sdk                     *fabsdk.FabricSDK
	ChannelClient           *channel.Client
	LedgerClient            *ledger.Client
}

type BlockchainForFabricInfo struct {
	ChannelID      string
	ChaincodeID    string
	OrgName        string
	UserId         string
	ConfigFilePath string
}

type FuctionName struct {
	GetRecordByKey                    string
	PutStringRecordByKey              string
	GetRecordBySelectorWithPagination string
	GetRecordCountBySelector          string
	GetHistoryByKey                   string
}

func (blc *BlockchainForFabric) Init(configFilePath string) error {
	// initFuctionName
	blc.fuctionName = initFuctionName()
	// init sdk
	configProvider := config.FromFile(configFilePath)
	sdk, err := fabsdk.New(configProvider)
	if err != nil {
		log.Println("Failed to create new fabsdk FabricSDK ", err)
		return err
	}
	blc.Sdk = sdk
	// init channel
	user := fabsdk.WithUser(blc.BlockchainForFabricInfo.UserId)
	org := fabsdk.WithOrg(blc.BlockchainForFabricInfo.OrgName)
	clientChannelContext := sdk.ChannelContext(blc.BlockchainForFabricInfo.ChannelID, user, org)

	// 客户端
	client, err := channel.New(clientChannelContext)
	if err != nil {
		log.Println("Failed to create new channel client ", err)
		return err
	}
	blc.ChannelClient = client

	// 账本
	ledgerclient, err := ledger.New(clientChannelContext)
	if err != nil {
		log.Println("Failed to create new channel client ", err)
		return err
	}
	blc.LedgerClient = ledgerclient

	return nil
}

func ExecuteCC(client *channel.Client, ccID string, fcn string, args [][]byte) (channel.Response, error) {
	request, err := client.Execute(channel.Request{ChaincodeID: ccID, Fcn: fcn, Args: args},
		channel.WithRetry(retry.DefaultChannelOpts))
	if err != nil {
		log.Println("Failed to move funds ", err)
	}
	return request, err
}

func UnmarshalTransaction(transaction *pb.ProcessedTransaction) {

}

// ref : https://blog.csdn.net/AlexTan_/article/details/110826476
func UnmarshalBlock(block *common.Block) (transactionActionList []*TransactionAction, err error) {
	bytes := block.Data.Data
	transactionActionList = []*TransactionAction{}
	for i := 0; i < len(bytes); i++ {
		envelope, err := GetEnvelopeFromBlock(bytes[i])
		if err != nil {
			return []*TransactionAction{}, errors.WithMessage(err, "unmarshaling envelope error: ")
		}
		payload, err := GetPayloadFromEnvelope(envelope)
		if err != nil {
			return []*TransactionAction{}, errors.WithMessage(err, "unmarshaling payload error: ")
		}
		tran, err := UnmarshalPayload(payload)
		transactionActionList = append(transactionActionList, tran)
	}
	return transactionActionList, nil
}

func GetEnvelopeFromBlock(data []byte) (*common.Envelope, error) {
	var err error
	env := &common.Envelope{}
	if err = proto.Unmarshal(data, env); err != nil {
		return nil, errors.Wrap(err, "error unmarshaling Envelope")
	}
	return env, nil
}

func GetPayloadFromEnvelope(envelope *common.Envelope) (payload *common.Payload, err error) {
	payload = &common.Payload{}
	err = proto.Unmarshal(envelope.Payload, payload)
	if err != nil {
		return &common.Payload{}, errors.WithMessage(err, "unmarshaling Payload error: ")
	}
	return payload, nil
}

func UnmarshalPayload(payload *common.Payload) (tran *TransactionAction, err error) {
	//解析channelHeader
	channelHeader := &common.ChannelHeader{}
	err = proto.Unmarshal(payload.Header.ChannelHeader, channelHeader)
	if err != nil {
		return &TransactionAction{}, errors.WithMessage(err, "unmarshaling ChannelHeader error: ")
	}
	transaction := &pb.Transaction{}
	err = proto.Unmarshal(payload.Data, transaction)
	if err != nil {
		return &TransactionAction{}, errors.WithMessage(err, "unmarshaling payload.Data error: ")
	}
	//解析transactionAction
	transactionAction, err := GetTransactionActionFromTransactionDeep(transaction.Actions[0])
	if err != nil {
		fmt.Println("GetTransactionActionFromTransactionDeep failed")
	}
	transactionAction.TxId = channelHeader.TxId
	transactionAction.Type = string(channelHeader.Type)
	transactionAction.Timestamp = time.Unix(channelHeader.Timestamp.Seconds, 0).Format("2020-10-22 18:09:04")
	transactionAction.ChannelId = channelHeader.ChannelId
	return transactionAction, nil

}

type Transaction struct {
	TransactionActionList []*TransactionAction `json:"transactionActionList"` //交易列表
}

type TransactionAction struct {
	TxId         string   `json:"txId"`         //交易ID
	BlockNum     uint64   `json:"blockNum"`     //区块号
	Type         string   `json:"type"`         //交易类型
	Timestamp    string   `json:"timestamp"`    //交易创建时间
	ChannelId    string   `json:"channelId"`    //通道ID
	Endorsements []string `json:"endorsements"` //背书组织ID列表
	ChaincodeId  string   `json:"chaincodeId"`  //链代码名称
	ReadSetList  []string `json:"readSetList"`  //读集
	WriteSetList []string `json:"writeSetList"` //写集
}

func GetTransactionActionFromTransactionDeep(transactionAction *pb.TransactionAction) (*TransactionAction, error) {

	//解析ChaincodeActionPayload 1
	ChaincodeActionPayload := &pb.ChaincodeActionPayload{}
	err := proto.Unmarshal(transactionAction.Payload, ChaincodeActionPayload)
	if err != nil {
		fmt.Printf("block unmarshal err: %s\n", err)
	}

	//解析ProposalResponsePayload 1.2
	ProposalResponsePayload := &pb.ProposalResponsePayload{}
	ChaincodeAction := &pb.ChaincodeAction{}
	chaincodeId := ""
	NsReadWriteSetList := []*rwset.NsReadWriteSet{}
	ReadWriteSetList := []*kvrwset.KVRWSet{}
	readSetList := []string{}
	writeSetList := []string{}
	if ChaincodeActionPayload.GetAction() != nil {
		err = proto.Unmarshal(ChaincodeActionPayload.Action.ProposalResponsePayload, ProposalResponsePayload)
		if err != nil {
			fmt.Printf("block unmarshal err: %s", err)
		}
		//解析ChaincodeAction 1.2.1
		err = proto.Unmarshal(ProposalResponsePayload.Extension, ChaincodeAction)
		if err != nil {
			fmt.Printf("block unmarshal err: %s", err)
		}

		chaincodeId = ChaincodeAction.ChaincodeId.Name
		//解析TxReadWriteSet	1.2.1.1
		TxReadWriteSet := &rwset.TxReadWriteSet{}
		err = proto.Unmarshal(ChaincodeAction.Results, TxReadWriteSet)
		if err != nil {
			fmt.Printf("block unmarshal err: %s", err)
		}
		//解析TxReadWriteSet	1.2.1.1.1
		for i := range TxReadWriteSet.NsRwset {
			ReadWriteSet := &kvrwset.KVRWSet{}
			//解析ReadWriteSet	1.2.1.1.1.1
			err = proto.Unmarshal(TxReadWriteSet.NsRwset[i].Rwset, ReadWriteSet)
			if err != nil {
				fmt.Printf("block unmarshal err: %s", err)
			}

			//解析读集
			for i := range ReadWriteSet.Reads {
				readSetJsonStr, err := json.Marshal(ReadWriteSet.Reads[i])
				if err != nil {
					fmt.Printf("block unmarshal err: %s", err)
				}
				readSetList = append(readSetList, string(readSetJsonStr))
			}

			//解析写集
			for i := range ReadWriteSet.Writes {
				writeSetItem := map[string]interface{}{
					"Key":      ReadWriteSet.Writes[i].GetKey(),
					"Value":    string(ReadWriteSet.Writes[i].GetValue()),
					"IsDelete": ReadWriteSet.Writes[i].GetIsDelete(),
				}

				writeSetJsonStr, err := json.Marshal(writeSetItem)
				if err != nil {
					fmt.Printf("block unmarshal err: %s", err)
				}
				writeSetList = append(writeSetList, string(writeSetJsonStr))

			}

			ReadWriteSetList = append(ReadWriteSetList, ReadWriteSet)
			NsReadWriteSetList = append(NsReadWriteSetList, TxReadWriteSet.NsRwset[i])
		}

	} else {
		chaincodeId = "没有交易数据"
	}

	//log.Println("数据:"+fmt.Sprintf("%s\n",ChaincodeActionPayload.Action.GetEndorsements()[0] ))
	//解析Endorsements 1.3
	endorsements := []string{}
	if ChaincodeActionPayload.Action.GetEndorsements() != nil {
		for i := range ChaincodeActionPayload.Action.GetEndorsements() {
			endorser := &msp.SerializedIdentity{}
			err = proto.Unmarshal(ChaincodeActionPayload.Action.Endorsements[i].Endorser, endorser)
			if err != nil {
				fmt.Printf("block unmarshal err: %s", err)
			}

			endorsements = append(endorsements, string(endorser.Mspid))
		}
	}

	transactionActionObj := TransactionAction{
		Endorsements: endorsements,
		ChaincodeId:  chaincodeId,
		ReadSetList:  readSetList,
		WriteSetList: writeSetList,
	}

	return &transactionActionObj, nil
}
